/*
 *  Stream Recorder: Records RealPlayer(tm) streams as MP3s
 *  Copyright (C) 2007  David Griffiths
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package dg.radio2ipod.controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Method;
import javax.swing.Timer;

/**
 *
 * @author dgriffiths
 */
public class Animator implements ActionListener {
    private int div;
    private int max;
    private int min;
    private int diff;
    private int target;
    private double damping = 0.8;
    private double mass = 0.05;
    private double velocity;
    private Object animatee;
    private String attributeName;
    private static final double MAX_SPEED = 150.0;
    private static final int FPS = 20;
    private static final int PAUSE = 1000 / FPS;
    private final Timer timer = new Timer(PAUSE, this);

    public Animator(Object theAnimatee, String attrName) {
        try {
            this.div = get(theAnimatee, attrName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.animatee = theAnimatee;
        this.attributeName = attrName;
    }

    public void start() {
        if (isRunning()) {
            timer.stop();
        }
        try {
            this.div = get(animatee, attributeName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        velocity = 0;
        timer.start();
    }

    public boolean isRunning() {
        return timer.isRunning();
    }

    public void actionPerformed(ActionEvent evt) {
        try {
            diff = target - div;
            double force = diff / 150.0;
            velocity += (force / mass);
            velocity *= damping;
            if (velocity > MAX_SPEED) {
                velocity = MAX_SPEED;
            }
            if (velocity < -MAX_SPEED) {
                velocity = -MAX_SPEED;
            }
            int nextPos = (int)(div + velocity);
            if ((nextPos >= getMax()) && (velocity > 0)) {
                velocity = -velocity;
                velocity *= 0.5;
            }
            if ((nextPos <= getMin()) && (velocity < 0)) {
                velocity = -velocity;
                velocity *= 0.5;
            }
            if ((Math.abs(diff) < 3) && (Math.abs(force) < 1.0)
            && (Math.abs(velocity) < 1.0)) {
                setValue(target);
                timer.stop();
                return;
            }
            div = nextPos;
            System.out.println("div = " + div);
            setValue(div);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setValue(final int x) throws Exception {
        set(animatee, attributeName, new Integer(x));
    }

    public int getTarget() {
        return target;
    }

    public void setTarget(int target) {
        this.target = target;
    }

    private static String getSetter(final String propertyName) {
        return "set" + propertyName.substring(0, 1).toUpperCase()
        + propertyName.substring(1);
    }

    private static String getGetter(final String propertyName) {
        return "get" + propertyName.substring(0, 1).toUpperCase()
        + propertyName.substring(1);
    }

    private static String getIsser(final String propertyName) {
        return "is" + propertyName.substring(0, 1).toUpperCase()
        + propertyName.substring(1);
    }

    private static Method getMethod(final Object object,
            final String methodName) {
        try {
            Method[] methods = object.getClass().getMethods();
            Method method = null;
            for (int i = 0; i < methods.length; i++) {
                if (methodName.equals(methods[i].getName())) {
                    Class[] types = methods[i].getParameterTypes();
                    if (types.length == 0) {
                        method = methods[i];
                        break;
                    }
                    if ((types.length == 1) && (types[0] == int.class)) {
                        method = methods[i];
                        break;
                    }
                }
            }
            if (method == null) {
                throw new RuntimeException("Can't find method " + methodName
                        + " on " + object);
            }
            return method;
        } catch (Exception e) {
            throw new RuntimeException("Binding failed: " + e.getMessage());
        }
    }

    public void set(Object model, String viewProperty, Object value)
    throws Exception {
        String setter = getSetter(viewProperty);
        Method viewSetter = getMethod(model, setter);
        viewSetter.invoke(model, new Object[] {value});
    }

    public int get(Object model, String viewProperty) throws Exception {
        String getter = getGetter(viewProperty);
        Method viewGetter = getMethod(model, getter);
        Integer value = (Integer)viewGetter.invoke(model, (Object[]) null);
        return value.intValue();
    }

    public double getDamping() {
        return damping;
    }

    public void setDamping(double damping) {
        this.damping = damping;
    }

    public double getMass() {
        return mass;
    }

    public void setMass(double mass) {
        this.mass = mass;
    }

    public int getMax() {
        return max;
    }

    public void setMax(int max) {
        this.max = max;
    }

    public int getMin() {
        return min;
    }

    public void setMin(int min) {
        this.min = min;
    }
}